/*
 * location.c
 *
 *  Created on: 2012-05-21
 *      Author: Aco
 */

#include "location.h"
#include "node.h"
#include "logger.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Prekomplikovano brojanje nivoa u tekstualnoj adresi, po broju zapeta
int cl_countLevels(const char * addr)
{
	int len = strlen(addr), n = 0;
	const char *ptr;
	if(len == 0) return 0;

	n = 1;
	ptr = strchr(addr, ',');
	if(ptr)
	{
		while((ptr = strchr(ptr + 1, ','))) n++;
		n++;
	}
	return n;
}

// Konstruisi ROOT node
CLocation * cl_createRoot()
{
	CLocation * root = (CLocation*)calloc(1, sizeof(CLocation));
	strcpy(root->name, "[ROOT]");
	return root;
}

// Trim stringa
void cl_rTrim(char * txt)
{
	int len, i;
	if(txt == NULL) return;
	len = strlen(txt);
	for(i = len - 1; i >= 0; --i)
		if(txt[i] == ' ')
			txt[i] = '\0';
		else
			break;
}

// Trim stringa
void cl_lTrim(char * text)
{
	int len = strlen(text);
	while(*text == ' ' && len > 0)
	{
		int i;
		for(i = 0; i < len; ++i)
			text[i] = text[i + 1];
		len--;
	}
}

// Vadjenje parceta adrese, do prve zapete
int cl_getAddrPart(const char * addr, char * part)
{
	char *comma;

	while(*addr == ' ') addr++;

	comma = strchr(addr, ',');
	if(comma)
	{
		strncpy(part, addr, comma - addr);
		part[comma - addr] = '\0';
	}
	else
	{
		strcpy(part, addr);
	}

	cl_rTrim(part);
	return strlen(part);
}

// Za slucajeve kada je potrebno obraditi sve lokacije u stablu nekom funkcijom,
// ona ce biti rekurzivno pozvana po datom pokazivacu za sve lokacije pojedinacno
void cl_traverseTree(CLocation * root, void (*func) (CLocation*,void*), void * param)
{
	CLocation *child;
	if(func == NULL) return;
	if(root == NULL) return;

	func(root, param);

	child = root->firstChild;
	while(child)
	{
		cl_traverseTree(child, func, param);
		child = child->next;
	}
}

// Puno preturanja charova i stringova da se dobije lijepo ASCII-art stablo
void cl_dumpAux(int id, CLocation * loc, char * lflags, int startLevel)
{
	int l = loc->level, i;
	CLocation * c = loc->firstChild;
	char logBuffer[512] = {0};

	for(i = startLevel; i < l; ++i)
	{
		if(i == startLevel)
		{
			strcat(logBuffer, "   ");
		}
		else
		{
			if(lflags[i])
				strcat(logBuffer, " | ");
			else
				strcat(logBuffer, "   ");
		}
	}

	if(loc->firstChild != NULL)
	{
		if(loc->next != NULL)
			strcat(logBuffer, " +--+ ");
		else
			strcat(logBuffer, " `--+ ");
	}
	else
	{
		if(loc->next != NULL)
			strcat(logBuffer, " +--- ");
		else
			strcat(logBuffer, " `--- ");
	}

	if(loc->numNodes > 0)
	{
		char buffer[512] = {0};
		char b2[64] = {0};
		int i;
		for(i = 0; i < loc->numNodes; ++i)
		{
			sprintf(b2, " (%s:%d|%d)", loc->node[i]->addrText, loc->node[i]->port, loc->node[i]->role);
			strcat(buffer, b2);
		}

		strcat(logBuffer, loc->name);
		strcat(logBuffer, " ");
		if(loc->kelvins > 0)
		{
			sprintf(b2, "<%.2f C> ", (float)loc->kelvins / 100.0f - 273.15f);
			strcat(logBuffer, b2);
		}
		strcat(logBuffer, buffer);
	}
	else
	{
		strcat(logBuffer, loc->name);
	}

	lputs(id, logBuffer);

	lflags[loc->level] = loc->next != NULL;

	i = 1;

	while(c)
	{
		cl_dumpAux(id, c, lflags, startLevel);
		c = c->next;
	}
}

// Pomocna za crtanje stabla, ova se poziva spolja
void cl_dumpTree(int id, CLocation * root)
{
	char flags[32] = {0};
	if(root == NULL) { printf("{NULL tree node}\n"); fflush(stdout); return; }
	cl_dumpAux(id, root, flags, root->level);
	fflush(stdout);
}

// Potrazi u lokaciji 'root' dijete koje odgovara adresi 'addr'
CLocation * cl_findAt(CLocation * root, const char * addr)
{
	CLocation * child;
	char part[64];
	if(root == NULL || addr == NULL) return NULL;

	cl_getAddrPart(addr, part);
	child = root->firstChild;
	while(child)
	{
		if(strcmp(child->name, part) == 0)
			return child;

		child = child->next;
	}

	return NULL;
}

// Prolazak kroz vezanu listu u potrazi za zadnjim djetetom
CLocation * cl_getLastChild(CLocation * child)
{
	if(child == NULL) return NULL;
	while(child->next) child = child->next;
	return child;
}

// Pretraga/kreiranje cvora u stablu lokacija po adresi
CLocation * cl_auxCreate(CLocation * _lroot, const char * addr, int allowCreate)
{
	CLocation *r, *c;
	int level = 1, plen = 0;
	char part[64];

	if(addr == NULL) return NULL;

	if(strchr(addr, ':'))	// Vjerovatno greska u razdvajanju poruke, ne bi trebalo biti :
	{
		printf("lolwut? : in location?");
	}

	r = _lroot;

	while(addr)
	{
		plen = cl_getAddrPart(addr, part);	// Uzima dio po dio adrese
		if(plen == 0)
		{
			printf("Empty address part! \"%s\"\n", part);
			return NULL;
		}

		c = cl_findAt(r, addr);
		if(c == NULL) // Ako nema tog cvora, pravi ga
		{
			if(!allowCreate) return NULL;	// Osim ako je pretraga u pitanju
			c = (CLocation*)calloc(1, sizeof(CLocation));

			if(r->firstChild == NULL)
				r->firstChild = c;
			else
				cl_getLastChild(r->firstChild)->next = c;	// Prikaci na roditelja

			c->level = level;
			strcpy(c->name, part);
			c->parent = r;
		}

		addr = strchr(addr, ',');
		if(addr)
			addr++;
		else
			return c;

		level++;	// Idemo dalje
		r = c;
	}

	return NULL;
}

// Pravljenje lokacije
CLocation * cl_createLocation(CLocation * root, const char * addr)
{
	return cl_auxCreate(root, addr, 1);
}

// Pretraga / zabranjeno pravljenje ako ne postoji vec
CLocation * cl_getByAddr(CLocation * root, const char * addr)
{
	return cl_auxCreate(root, addr, 0);
}

// Brojanje djece u vezanoj listi
int cl_countChildren(CLocation * loc)
{
	int count = 0;
	CLocation * child;
	if(loc == NULL) return 0;
	child = loc->firstChild;
	while(child)
	{
		count++;
		child = child->next;
	}
	return count;
}

// Brojanje prijavljenih nodova u komsiluku
int cl_countNodes(CLocation * loc)
{
	int count = 0;
	CLocation * child;
	if(loc == NULL) return 0;
	child = loc->firstChild;
	while(child)
	{
		count += child->numNodes;
		child = child->next;
	}
	return count;
}

// Sastavi punu tekstualnu adresu od cvora iz stabla
void cl_constructFullAddress(CLocation * loc, char * text)
{
	CLocation *parent;
	strcpy(text, loc->name);

	parent = loc->parent;
	while(parent)
	{
		if(parent->parent != NULL)
		{
			strcat(text, ", ");
			strcat(text, parent->name);
		}
		parent = parent->parent;
	}
}

// Obrni dijelove adrese, po "protokolu" je da idu obrnuto od stabla
void cl_reverseAddress(const char * src, char * dst)
{
	int n = 0, i;
	char parts[32][64];
	const char *next = NULL;

	next = strchr(src, ',');
	if(next == NULL) { strcpy(dst, src); return; }

	if(strchr(src, ':'))
	{
		puts("lolwut?!");
	}

	for(i = 0; i < 32; ++i) memset(parts[i], 0, 64);

	while(next)
	{
		int len;
		next++;
		while(*src == ' ' && *src) src++;
		len = next - src - 1;
		strncpy(parts[n], src, len);
		parts[n][len] = '\0';
		n++;
		src = next;
		next = strchr(src, ',');
		if(next == NULL)
		{
			strcpy(parts[n], src);
			n++;
		}
		cl_lTrim(parts[n]);
		cl_rTrim(parts[n]);
	}

	dst[0] = '\0';
	for(i = n - 1; i > 0; --i)
	{
		strcat(dst, parts[i]);
		strcat(dst, ", ");
	}
	strcat(dst, parts[0]);
	cl_lTrim(dst);
}

// Prikaci node na lokaciju, ako ima mjesta
int cl_attachNode(CLocation * loc, CNode * node, int max)
{
	if(loc == NULL) return 0;
	if(node == NULL) return 0;
	if(loc->numNodes >= max) return 0;
	if(max > 8) max = 8;
	loc->node[loc->numNodes] = node;
	loc->numNodes++;
	return 1;
}

// Otkaci node iz lokacije
int cl_detachNode(CLocation * loc, CNode * node)
{
	int i = 0, j;
	if(loc == NULL) return 0;
	if(node == NULL) return 0;
	for(i = 0; i < loc->numNodes; ++i)
	{
		if(loc->node[i] == node)
		{
			for(j = i; j < loc->numNodes - 1; ++j)
				loc->node[j] = loc->node[j + 1];
			loc->node[loc->numNodes - 1] = NULL;
			loc->numNodes--;
			return 1;
		}
	}
	return 0;
}

// Poredi nodove, po IP adresi i portu
int cl_cmpNode(CNode * n1, CNode * n2)
{
	if(n1 == NULL || n2 == NULL) return 0;
	return n1->addrInt == n2->addrInt && n1->port == n2->port;
}

// Potrazi node u datoj lokaciji
CNode * cl_findNode(CLocation * loc, unsigned int ipAddr, int port)
{
	int i = 0;
	if(loc == NULL) return NULL;
	for(i = 0; i < loc->numNodes; ++i)
		if(loc->node[i]->addrInt == ipAddr && loc->node[i]->port == port)
			return loc->node[i];
	return NULL;
}

// Potrazi node u podstablu
CNode * cl_recFindNode(CLocation * loc, unsigned int ipAddr, int port)
{
	CNode * res;
	CLocation *child;
	if(loc == NULL) return NULL;

	res = cl_findNode(loc, ipAddr, port);
	if(res) return res;

	child = loc->firstChild;
	while(child)
	{
		res = cl_recFindNode(child, ipAddr, port);
		if(res) return res;
		child = child->next;
	}
	return NULL;
}

// Trazi node medju precima
CNode * cl_upwardFindNode(CLocation * loc, unsigned int ipAddr, int port)
{
	if(loc == NULL) return NULL;
	CNode * res;

	while(loc)
	{
		res = cl_findNode(loc, ipAddr, port);
		if(res)
			return res;
		loc = loc->parent;
	}
	return NULL;
}

// Trazi node na obije strane u stablu
CNode * cl_bidirFindNode(CLocation * loc, unsigned int ipAddr, int port)
{
	CNode * res;
	if(loc == NULL) return NULL;
	res = cl_upwardFindNode(loc, ipAddr, port);
	if(res)
		return res;
	else
		return cl_recFindNode(loc, ipAddr, port);
}

// Da li je 'parent' predak 'child'-a?
int cl_isAncestorOf(CLocation * parent, CLocation * child)
{
	do
	{
		child = child->parent;
		if(child == parent) return 1;
	} while(child);
	return 0;
}

// Da li imaju istog roditelja?
int cl_isSiblingOf(CLocation * l1, CLocation * l2)
{
	if(l1->parent == NULL || l2->parent == NULL) return 0;
	return l1->parent == l2->parent;
}

// Najvisa lokacija na kojoj se nalazi node?
CLocation * cl_getTopLevel(CNode * n, CLocation * loc)
{
	if(n == NULL || loc == NULL) return NULL;
	do
	{
		if(!cl_findNode(loc->parent, n->addrInt, n->port))
			return loc;
		else
			loc = loc->parent;
	} while(loc);
	return NULL;
}
